/*!
 *  \file       Fft2D.cpp
 *  \brief      Implementazione della classe \c Fft2D
 *  \author     Davide Scola <S2299453@studenti.unige.it>
 *  \author	Minetti Alberto <S3064777@studenti.unige.it>
 *  \version    1.0
 *
 */
#include <iostream>
#include <map>
#include <vector>
#include <complex>
#include "Fft.cpp"
#include "SimpleMatrix.hpp"
#include "Fft2D.hpp"
#include "InvalidSizeException.hpp"

using namespace std;


/*!
 *  \brief      Calcola la Fft su tutte le righe
 */
template < class tType > void
Fft2D< tType >::computeRows( void )
{
  Fft< tType > fft( this->cols_ );
  vector< complex_t > swap( this->cols_ );
  
  for( unsigned long int iRow = 0; iRow < this->rows_; ++iRow )
  {
    fft.compute( this->rowAt( iRow, swap ) );
    this->assignRow( iRow, fft );
  }
}

/*!
 *  \brief      Calcola la Fft inversa su tutte le righe
 */
template < class tType > void
Fft2D< tType >::invertRows( void )
{
  Fft< tType > fft( this->cols_ );
  
  for( unsigned long int iRow = 0; iRow < this->rows_; ++iRow )
  {
    this->rowAt( iRow, fft );
    fft.invert( );
    this->assignRow( iRow, fft );
  }
}

/*!
 *  \brief      Calcola la Fft su tutte le colonne
 */
template < class tType > void
Fft2D< tType >::computeCols( void )
{
  Fft< tType > fft( this->rows_ );
  vector< complex_t > swap( this->rows_ );
  
  for( unsigned long int iCol = 0; iCol < this->cols_; ++iCol )
  {
    fft.compute( this->colAt( iCol, swap ) );
    this->assignCol( iCol, fft );
  }
}

/*!
 *  \brief      Calcola la Fft inversa su tutte le colonne
 */
template < class tType > void
Fft2D< tType >::invertCols( void )
{
  Fft< tType > fft( this->rows_ );
  
  for( unsigned long int iCol = 0; iCol < this->cols_; ++iCol )
  {
    this->colAt( iCol, fft );
    fft.invert( );
    this->assignCol( iCol, fft );
  }
}

/*!
 *  \brief      Inizializza la function table degli operatori
 */
template < class tType > void
Fft2D< tType >::initOperators( void )
{
  this->operators_.insert( pair< int, fnOperator_t >( OP_SQUAREROOT, &Fft2D< tType >::op_squareRoot ));
  this->operators_.insert( pair< int, fnOperator_t >( OP_LOGARITHMIC, &Fft2D< tType >::op_logarithmic ));
}

/*!
 *  \brief      Applica gli operatori richiesti all'immagine data
 *  \param[in]		flags		operatori da applicare
 *  \param[in,out]	image		immagine da manipolare
 */
template < class tType > SimpleMatrix< tType >&
Fft2D< tType >::marshall( unsigned long int flags, SimpleMatrix< tType >& image )
{
  typename map< unsigned long int, fnOperator_t >::const_iterator
    iter = this->operators_.begin();
  
  while( iter != this->operators_.end() ) {
    if( flags & iter->first ) {
      (this->*(iter->second))( image );
    }
    
    ++iter;
  }
  
  return image;
}


/*!
 *  \brief      Costruttore di \c Fft2D
 *  \param[in]	image		immagine da elaborare
 *
 *  \exception InvalidSizeException	se almeno una delle due dimensioni e` nulla
 */
template < class tType >
Fft2D< tType >::Fft2D( const SimpleMatrix< tType >& image )
    : SimpleMatrix< complex_t >( image.getRows(), image.getCols() )
{
  // controllare che la matrice sia quadrata e che la dimensione sia una potenza di due
  for(unsigned int iRow = 0; iRow < this->rows_; ++iRow)
    for(unsigned int iCol = 0; iCol < this->cols_; ++iCol)
      (*this)( iRow, iCol ) = complex_t( image( iRow, iCol ), 0 );
  
  this->initOperators();
}

/*!
 *  \brief      Calcola la Fft2D dell'immagine
 */
template < class tType > void
Fft2D< tType >::compute( void )
{
  this->computeRows( );
  this->computeCols( );
}

/*!
 *  \brief	Crea un'immagine della FFT2D (magnitude)
 *  \param[in]	flags		operatori da applicare all'immagine
 *  \param[out]	image		immagine da ritornare
 */
template < class tType > SimpleMatrix< tType >&
Fft2D< tType >::outMagnitude( unsigned long int flags, SimpleMatrix< tType >& image )
{
  for( unsigned int i = 0; i < this->rows_; ++i )
  {
    for( unsigned int j = 0; j < this->cols_; ++j )
    {
      image( i, j ) = abs( (*this)(i, j) );
    }
  }
  
  return this->marshall( flags, image );
}

/*!
 *  \brief      Calcola la FFT2D inversa
 */
template < class tType > void
Fft2D< tType >::invert( void )
{
  this->invertCols( );
  this->invertRows( );
}

/*!
 *  \brief      Operatore logaritmo: tutti i valori scalati ad un massimo valore di 255
 *  \param[in]	image		immagine a cui applicare l'operatore
 */
template < class tType > void
Fft2D< tType >::op_logarithmic( SimpleMatrix< tType >& image )
{
  tType factor = 255 / log( 1 + abs( image.findMax() ) );
  
  for( unsigned int i = 0; i < image.getRows(); ++i )
  {
    for( unsigned int j = 0; j < image.getCols(); ++j )
    {
      image( i, j ) = factor * log( 1 + abs( image( i, j ) ) );
    }
  }
}

/*!
 *  \brief      Operatore square-root: tutti i valori scalati sulla propria radice quadrata
 *  \param[in]	image		immagine a cui applicare l'operatore
 */
template < class tType > void
Fft2D< tType >::op_squareRoot( SimpleMatrix< tType >& image )
{
  for( unsigned int i = 0; i < image.getRows(); ++i )
  {
    for( unsigned int j = 0; j < image.getCols(); ++j )
    {
      image( i, j ) = sqrt( image( i, j ) );
    }
  }
}
